{$M 3072,26624,26624}
{
	3k heap + 26k stack + 37k .exe
	equals 66k memory footprint!
}

program paku;

{
	PAKU PAKU, A TXTGRAPH Game
	Jason M. Knight, 2011
	Revision 1.3

	New in this release:
		Joystick support
		Fixed PC Jr. "blink/intensity" error
		Save top five high scores to disk
		New faster/lower memory use font renderer
		Tweaked level transition 'blink' using the twin-buffers

	deathshadow60@hotmail.com
	http://my.opera.com/deathshadow/blog/
	http://www.deathshadow.com

}

uses
	jfunc,
	joystick,
	timer,
	sound,
	txtGraph;

const
	revision='1.3';
	buildDate='14 February, 2011';
	makerString='PALADIN_SYSTEMS';
	crlf=#13+#10;
	titleString=crlf+'PAKU PAKU By Jason M. Knight, Paladin Systems North. '+crlf+
		'Version '+revision+' - '+buildDate+crlf;

type
	initials=string[3];

	tPlayerScore=record
		points:longint;
		name:initials;
		system:boolean;
	end;

	tScoreList=object
		data:array[0..4] of tPlayerScore;
		lastScore:longint;
		f:file;
		constructor init;
		procedure update;
		destructor term;
	end;

	gameSprite=object
		tile:integer;
		cSprite:pSprite;
		constructor init(sx,sy,sTile:integer);
	end;

	tBonus=object(gameSprite)
		showCounter,
		direction:integer;
		constructor init;
		procedure reset;
		procedure activate;
		procedure update;
	end;

	tPlayer=object(gameSprite)
		direction,
		inputDirection,
		altInputDirection,
		waka,wakaDir:integer;
		lastInputDirX:boolean;
		constructor init;
		procedure reset;
		procedure update;
	end;

	pGhost=^tGhost;
	tGhost=object(gameSprite)
		direction,inputDirection,
		tileOffset,activeTileOffset,
		logic,
		mode,
		frame,
		fleeCounter,
		exitCounter,
		homeX,homeY:integer;
		alternate,inJail:boolean;
		constructor init(sLogic:integer);
		procedure reset;
		procedure update;
	end;

	tGhostData=record
		nickname,behavior:string[7];
		color:byte;
		cx,cy,d,hx,hy:integer;
	end;

	tLevelData=record
		symbol,points,rate,frightTime,flashes:integer;
	end;

	tBehavior=record
		behavior,time:word;
	end;
	tBehaviorList=array[0..7] of tBehavior;

	tPelletData=record
		x,y,
		tileX,
		tileY:word;
	end;

	tTextLine=record
		x,y:word;
		data:string[24];
	end;

	tTextLineShort=record
		x,y:word;
		data:string[14];
	end;

const
	readyString='READY!';

	{ difficulty settings }
	pointsPerLife=10000;
	startingLives=3;
	startingLevel=0;

	{game settings}
	startingDots=244;
	fruitCounter1=startingDots-70;
	fruitCounter2=fruitCounter1-100;
	elroy=20;
	superElroy=10;

	move_up 	 = 0;
	move_right = 1;
	move_down  = 2;
	move_left  = 3;

	mode_scatter = 0;
	mode_chase	 = 1;
	mode_flee 	 = 2;
	mode_eaten	 = 3;

	jail_left 	= 33;
	jail_right	= 52;
	jail_top		= 38;
	jail_bottom = 44;

	blinky=0;
	pinky=1;
	inky=2;
	clyde=3;

	fieldOffsetX=1;
	fieldWidth=28;
	fieldEndX=fieldWidth*3+fieldOffsetX;

	sirenLookup:array[0..23] of word=(
		440,466,494,523,554,587,622,659,698,740,784,831,
		880,831,784,740,698,659,622,587,554,523,494,466
	);

	ghostData:array[0..3] of tGhostData=(
		(nickname:'STINKY'; behavior:'HOUND'; 	color:4; cX:40; cY:32; d:move_left; hX:fieldWidth*3; hY:5),
		(nickname:'KINKY';	behavior:'HUNTER';	color:5; cX:41; cY:39; d:move_up; 	hX:1; 					 hY:5),
		(nickname:'HINKY';	behavior:'SCHIZO';	color:3; cX:34; cY:39; d:move_down; hX:fieldWidth*3; hY:85),
		(nickname:'BLAINE'; behavior:'ELUSIVE'; color:6; cX:47; cY:39; d:move_up; 	hX:1; hY:85)
	);

	levelData:array[1..21] of tLevelData=(
		(symbol:$38; points:100;	rate:6; frightTime:1320; flashes:600),
		(symbol:$39; points:300;	rate:5; frightTime:1200; flashes:600),
		(symbol:$3A; points:500;	rate:5; frightTime:1080; flashes:600),
		(symbol:$3A; points:500;	rate:5; frightTime:960;  flashes:600),

		(symbol:$3B; points:700;	rate:4; frightTime:840;  flashes:600),
		(symbol:$3B; points:700;	rate:4; frightTime:1320; flashes:600),
		(symbol:$3C; points:1000; rate:4; frightTime:840;  flashes:600),
		(symbol:$3C; points:1000; rate:4; frightTime:840;  flashes:600),

		(symbol:$3D; points:2000; rate:4; frightTime:480;  flashes:360),
		(symbol:$3D; points:2000; rate:4; frightTime:1200; flashes:600),
		(symbol:$3E; points:3000; rate:4; frightTime:840;  flashes:600),
		(symbol:$3E; points:3000; rate:4; frightTime:480;  flashes:360),

		(symbol:$3F; points:5000; rate:4; frightTime:480; flashes:360),
		(symbol:$3F; points:5000; rate:4; frightTime:960; flashes:600),
		(symbol:$3F; points:5000; rate:4; frightTime:480; flashes:360),
		(symbol:$3F; points:5000; rate:4; frightTime:480; flashes:360),

		(symbol:$3F; points:5000; rate:4; frightTime:0; 	flashes:0),
		(symbol:$3F; points:5000; rate:4; frightTime:480; flashes:360),
		(symbol:$3F; points:5000; rate:4; frightTime:0; 	flashes:0),
		(symbol:$3F; points:5000; rate:4; frightTime:0; 	flashes:0),

		(symbol:$3F; points:5000; rate:5; frightTime:0; 	flashes:0)
	);

	behaviorLevel1:tBehaviorList=(
		(behavior:mode_scatter; time:420),
		(behavior:mode_chase; time:1200),
		(behavior:mode_scatter; time:420),
		(behavior:mode_chase; time:1200),
		(behavior:mode_scatter; time:300),
		(behavior:mode_chase; time:1200),
		(behavior:mode_scatter; time:300),
		(behavior:mode_chase; time:0)
	);
	behaviorLevel2_4:tBehaviorList=(
		(behavior:mode_scatter; time:420),
		(behavior:mode_chase; time:1200),
		(behavior:mode_scatter; time:420),
		(behavior:mode_chase; time:1200),
		(behavior:mode_scatter; time:300),
		(behavior:mode_chase; time:65535),
		(behavior:mode_scatter; time:1),
		(behavior:mode_chase; time:0)
	);
	behaviorLevel5Plus:tBehaviorList=(
		(behavior:mode_scatter; time:300),
		(behavior:mode_chase; time:1200),
		(behavior:mode_scatter; time:300),
		(behavior:mode_chase; time:1200),
		(behavior:mode_scatter; time:300),
		(behavior:mode_chase; time:65535),
		(behavior:mode_scatter; time:1),
		(behavior:mode_chase; time:0)
	);

	themeBass:string[99]='VF,'+
		'1B2,R4,2B2,'+ {8}
		'1B2,R4,2B2,'+ {16}
		'1C2,R4,2C2,'+ {24}
		'1C2,R4,2C2,'+ {32}
		'1B2,R4,2B2,'+ {40}
		'1B2,R4,2B2,'+ {48}
		'1F#2,R2,1G#2,R2,'+ {56}
		'2A#2,R2,2B2,R2'; {64}

	themeLead:string[203]='VF,'+
		'2B1,R1,3B1,R1,2F#1,R1,2D#1,R1,'+ 	{8}
		'3B1,2F#1,R2,2D#3,R1,'+ 						{16}
		'2C1,R1,3C1,R1,2G1,R1,2E1,R1,'+ 		{24}
		'3C1,2G1,R2,2E3,R1,'+ 							{32}
		'2B1,R1,3B1,R1,2F#1,R1,2D#1,R1,'+ 	{40}
		'3B1,2F#1,R2,2D#3,R1,'+ 						{48}
		'2D#1,2E1,2F1,R1,2F1,2F#1,2G1,R1,'+ {56}
		'2G1,2G#1,3A1,R1,3B2,R2'; 					{64}

	pelletData:array[0..3] of tPelletData=(
		(x:4;  y:9;  tileX:1;  tileY:3),
		(x:79; y:9;  tileX:26; tileY:3),
		(x:79; y:69; tileX:26; tileY:23),
		(x:4;  y:69; tileX:1;  tileY:23)
	);

	borderColors:array[0..2] of byte=(
		15,12,4
	);

	menuTextCount=4;
	menuText:array[1..menuTextCount] of tTextLineShort=(
		(x:107; y:73; data:'10 POINTS'),
		(x:107; y:79; data:'50 POINTS'),
		(x:98;	y:15; data:'ENTER TO PLAY'),
		(x:103; y:21; data:'ESC TO EXIT')
	);

	scoreTextCount=5;
	scoreText:array[1..scoreTextCount] of tTextLineShort=(
		(x:43; y:18; data:'\EEXCELLENT!'),
		(x:43; y:27; data:'\BYOU HAVE THE'),
		(x:43; y:33; data:'\BHIGH SCORE!'),
		(x:43; y:42; data:'\FENTER YOUR'),
		(x:43; y:48; data:'\FINITIALS')
	);

	joyTextCount=6;
	joyText:array[1..joyTextCount] of tTextLine=(
		(x:0; y:22; data:'\EJOYSTICK CALIBRATION'),
		(x:0; y:39; data:'CENTER STICK AND PRESS'),
		(x:0; y:45; data:'\FBUTTON\7 OR \FSPACEBAR'),
		(x:0; y:51; data:'TO CALIBRATE'),
		(x:0; y:60; data:'OR \FESC\7 TO CANCEL AND'),
		(x:0; y:66; data:'DISABLE JOYSTICK')
	);

var
	score,
	highScore:longint;
	scoreList:tScoreList;

	lives,
	level,truncLevel,
	dots,
	ghostMode,
	nextGhost,
	behaviorStep,
	behaviorCounter,
	globalDotCounter,
	globalJailCounter,
	globalFleeCounter,
	pelletBlinkCounter,
	gainLifeCounter,
	fleeTotal,
	siren,pakuSound,
	frameSpeed,
	timerInterval:word;
	joyCenterX,joyCenterY,
	joyDeadX,joyDeadY:integer;
	joyScaleY:real;

	soundOn,joystickOn,joystickAllow,
	globalJailCounterEnable:boolean;

	map,
	mapTiles,
	spriteTiles:tileset;

	fonts:pFontSet;

	tileMap:array[0..30,0..fieldWidth-1] of byte;

	player:tPlayer;
	ghost:array[0..3] of tGhost;
	bonus:tBonus;

procedure wait(ticks:word);
begin
	userClockCounter:=0;
	repeat
		if userTimerExpired(timerInterval) then dec(ticks);
	until ticks<=0;
end;

procedure killVoices;
var
	t:word;
begin
	for t:=0 to 2 do outFreq(t,0,0,0);
end;

procedure drawScoreBox;
begin
	tg_rectangle(16,14,69,72,9);
	tg_bar(17,15,68,71,1);
	tg_putpixel(16,14,0);
	tg_putpixel(16,72,0);
	tg_putpixel(69,14,0);
	tg_putpixel(69,72,0);
end;

constructor tScoreList.init;
var
	t:word;
	n:longint;
begin
	assign(f,'SCORES.DAT');
	{$I-}
	reset(f,1);
	{$I+}
	if IOResult=0 then begin
		blockread(f,data,sizeof(data));
		blockread(f,lastScore,4);
		close(f);
	end else begin
		n:=80000;
		for t:=0 to 4 do with data[t] do begin
			name:=copy(makerString,t*3+1,3);
			points:=n;
			n:=n div 2;
			system:=true;
		end;
		lastScore:=0;
	end;
	highScore:=data[0].points;
end;

function getScoreName:initials;
var
	t:byte;
	ch:char;
	tst:st10;
begin
	tst:='';
	ch:=' ';
	t:=0;
	userClockCounter:=0;
	flushKeyBuffer;
	repeat
		if userTimerExpired(timerInterval) then begin
			case t of
				0:fonts^.outtextCentered(43,59,tst+'\9_',15);
				16:fonts^.outtextCentered(43,59,tst+'_',15);
			end;
			t:=(t+1) mod 32;
		end;
		if keypressed then begin
			ch:=readkey;
			case ch of
				'a'..'z',
				'A'..'Z',
				'0'..'9',
				#32:begin
					tst:=tst+upcase(ch);
					tg_waitRetrace;
					tg_bar(30,59,56,65,1);
					fonts^.outtextCentered(43,59,tst+'_',15);
					wait(30);
				end;
				#13:ch:=#27;
			end;
		end;
	until (ch=#27) or (length(tst)=3);
	tg_bar(30,59,56,65,1);
	fonts^.outtextCentered(43,59,tst,15);
	wait(120);
	getScoreName:=tst;
end;

procedure tScoreList.update;
var
	t,n,v:word;
	tempScore:longint;
begin
	lastScore:=score;
	t:=0;
	while (t<5) do with data[t] do begin
		if lastScore>points then begin
			n:=4;
			while (n>t) do begin
				move(data[n-1],data[n],sizeof(tPlayerScore));
				dec(n);
			end;
			points:=lastScore;
			system:=false;
			drawScoreBox;
			for v:=1 to scoreTextCount do with scoreText[v] do begin
				fonts^.outTextCentered(x,y,data,7);
			end;
			name:=getScoreName;
			t:=4;
		end;
		inc(t);
	end;
	highScore:=data[0].points;
end;

destructor tScoreList.term;
begin
	rewrite(f,1);
	blockwrite(f,data,sizeof(data));
	blockwrite(f,lastScore,4);
	close(f);
end;

procedure whitePlayField; assembler;
asm
	les  di,renderBuffer
	mov  cx,7520
	inc  di
	mov  dx,$0301
	mov  bx,$3010
@loop:
	mov  al,es:[di]
	mov  ah,al
	and  ah,dh
	cmp  ah,dl
	jne  @compare2
	or	 al,$07
@compare2:
	mov  ah,al
	and  ah,bh
	cmp  ah,bl
	jne  @writeIt
	or	 al,$70
@writeIt:
	stosb
	loop @loop
end;

procedure showDestPlayField; assembler;
asm
	mov  ax,$B800
	mov  es,ax
	mov  di,1
	mov  dx,ds { faster than a push/pop to just use dx }
	lds  si,destBuffer
	mov  cx,93
@loopRow:
	mov  bx,cx
	mov  cx,43
@loopPixel:
	movsb
	inc  di
	loop @loopPixel
	add  di,74
	add  si,37
	mov  cx,bx
	loop @loopRow
	mov  ds,dx
end;

function scoreString(n:longint):st10;
var
	d:longint;
	tst:string;
begin
	d:=10000000;
	tst:='';
	while (d>0) do begin
		tst:=tst+chr(48+((n div d) mod 10));
		d:=d div 10;
	end;
	scoreString:=tst;
end;

procedure scoreBox;
var
	t,n,b:byte;
begin
	drawScoreBox;
	fonts^.outText(23,18,'HIGH',11);
	fonts^.outText(40,18,'SCORES',11);
	fonts^.outTextCentered(43,57,'LAST SCORE',11);
	n:=25;
	for t:=0 to 4 do with scoreList.data[t] do begin
		if system then b:=9 else if t=0 then b:=15 else b:=7;
		fonts^.outText(19,n,scoreString(points),b);
		if (b=15) then dec(b);
		fonts^.outTextCentered(60,n,name,b);
		inc(n,6);
	end;
	fonts^.outTextCentered(43,64,scoreString(scoreList.lastScore),3);
end;

function calibrateStick(centerX:byte):boolean;
var
	ch:char;
	t,jx,jy:word;
	stx,sty:string[8];
	yy:byte;
begin
	jx:=joystickPosition(0);
	if (jx<stickFailPoint) then begin
		ch:='~';
		for t:=1 to joyTextCount do with joyText[t] do begin
			fonts^.outTextCentered(centerX+x,y,data,7);
		end;
		repeat
			jx:=joystickPosition(0);
			str(jx,stx);
			tg_waitRetrace;
			tg_bar(centerX-36,29,centerX-2,35,1);
			fonts^.outTextCentered(centerX-17,30,stx,$0A);
			jy:=joystickPosition(1);
			str(jy,sty);
			tg_waitRetrace;
			tg_bar(centerX+2,29,centerX+36,35,1);
			fonts^.outTextCentered(centerX+17,30,sty,$0A);
			if keypressed then ch:=readkey;
			if (
				(ch=#32) or
				joystickButton(0) or
				joystickButton(1)
			) then begin
				joystickDebounce;
				joyCenterX:=jx;
				joyCenterY:=jy;
				joyScaleY:=joyCenterX/joyCenterY;
				joyDeadX:=joyCenterX div 4;
				joyDeadY:=trunc(joyCenterY*joyScaleY/4);
				ch:=#32;
			end else if (ch=#27) then begin
				player.altInputDirection:=5;
			end;
		until (ch=#27) or (ch=#32);
		calibrateStick:=not(ch=#27);
	end else begin
		calibrateStick:=false;
		player.altInputDirection:=5;
	end;
end;

procedure checkStickDirection;
var
	jx,jy,ajx,ajy:integer;
begin
	player.altInputDirection:=5;
	{ remember the function returns word, so force type }
	jx:=integer(joystickPosition(0))-joyCenterX;
	ajx:=abs(jx);
	jy:=trunc((integer(joyStickPosition(1))-joyCenterY)*joyScaleY);
	ajy:=abs(jy);
	if ajx>ajy then begin
		if ajx>joyDeadX then begin
			if jx>0 then begin
				player.inputDirection:=move_right;
			end else	begin
				player.inputDirection:=move_left;
			end;
			if ((ajy*16) div ajx)>14 then begin
				if jy>0 then begin
					player.altInputDirection:=move_down;
				end else begin
					player.altInputDirection:=move_up;
				end;
			end;
		end;
	end else begin
		if ajy>joyDeadY then begin
			if jy>0 then begin
				player.inputDirection:=move_down;
			end else begin
				player.inputDirection:=move_up;
			end;
			if ((ajx*16) div ajy)>14 then begin
				if jx>0 then begin
					player.altInputDirection:=move_right;
				end else	begin
					player.altInputDirection:=move_left;
				end;
			end;
		end;
	end;
end;

procedure showAudioStatus;
begin
	tg_bar(107,94,142,98,0);
	fonts^.outText(107,94,'S\FO\7UND',7);
	if soundOn then begin
		fonts^.outtext(129,94,'ON',10);
	end else begin
		fonts^.outtext(129,94,'OFF',4);
	end;
end;

procedure checkGlobalKeys(ch:char);
begin
	case ch of
		'o','O':begin
			if soundOn then killVoices;
			soundOn:=not(soundOn);
			showAudioStatus;
		end;
	end;
end;

procedure gameKeyCheck;
var
	ch:char;
begin
	if keypressed then begin
		ch:=readkey;
		case ch of
			'w','W','8':player.inputDirection:=move_up;
			'd','D','6':player.inputDirection:=move_right;
			's','S','2':player.inputDirection:=move_down;
			'a','A','4':player.inputDirection:=move_left;
			'j','J':if joystickAllow then begin
				if soundOn then killVoices;
				tg_bar(0,0,84,92,1);
				joystickOn:=calibrateStick(43);
				showDestPlayfield;
				buffer_updateSprites;
				userClockCounter:=0;
			end;
			#0:begin
				ch:=readkey;
				case ch of
					#$48:player.inputDirection:=move_up;
					#$4D:player.inputDirection:=move_right;
					#$50:player.inputDirection:=move_down;
					#$4B:player.inputDirection:=move_left;
				end;
			end;
			#27:lives:=0;
			else checkGlobalKeys(ch);
		end;
	end;
end;

procedure playEatGhost;
var
	count:word;
begin
	if (soundOn) then begin
		count:=110;
		killVoices;
		userClockCounter:=0;
		while count<1760 do begin
			if userTimerExpired(timerInterval) then begin
				outFreq(0,count,$0F,0);
				inc(count,25);
			end;
			gameKeyCheck;
		end;
	end else wait(90);
end;

procedure playTheme;
var
	lead,bass:tSynthLine;
begin
	lead.init(@themeLead,100,0,1);
	bass.init(@themeBass,100,1,0);
	wait(20);
	userClockCounter:=0;
	repeat
		if userTimerExpired(timerInterval) then begin
			lead.playStep;
			bass.playStep;
		end;
		gameKeyCheck;
	until (lead.done and bass.done);
end;

procedure showLives;
var
	t,x:word;
begin
	t:=1;
	x:=2;
	while (t<lives) do begin
		tg_tile5(x,94,spriteTiles.dataStart,$4C);
		inc(x,6);
		inc(t);
	end;
	tg_bar(x,94,26,99,0);
end;

procedure scorePoints(points:longint);
var
	oldScore:longint;
	t:byte;
begin
	oldScore:=score;
	score:=score+points;
	fonts^.outDecPadded(108,36,score,3);
	if (score>highScore) then begin
		highScore:=score;
		fonts^.outDecPadded(108,18,highScore,3);
	end;
	if (oldScore mod pointsPerLife)>(score mod pointsPerLife) then begin
		if (lives<5) then begin
			inc(lives);
			gainLifeCounter:=90;
		end;
		showLives;
	end;
end;

constructor tBonus.init;
begin
	showCounter:=0;
	direction:=move_right;
	cSprite:=buffer_addSprite(150,90,$5F,5,@spriteTiles);
	with cSprite^ do begin
		currentX:=154;
		currentY:=0;
		currentTile:=$5F;
		showCounter:=0;
	end;
end;

procedure tBonus.reset;
begin
	with cSprite^ do begin
		buffer_copySourceDest8x8(currentX,currentY);
		bufferShow(currentX,currentY);
		currentX:=154;
		currentY:=0;
		oldX:=currentX;
		oldY:=currentY;
		currentTile:=$5F;
		showCounter:=0;
	end;
end;

procedure tBonus.activate;
begin
	with (cSprite^) do begin
		cSprite^.currentTile:=levelData[truncLevel].symbol;
		currentX:=40;
		currentY:=50;
	end;
	showCounter:=(1080+random(120)) div frameSpeed;
end;

procedure tBonus.update;
begin
	if (showCounter>0) then begin
		dec(showCounter);
	end else if not(cSprite^.currentTile=$5F) then reset;
end;

constructor gameSprite.init(sx,sy,sTile:integer);
begin
	tile:=sTile;
	cSprite:=buffer_addSprite(sx,sy,tile,5,@spriteTiles);
end;

constructor tPlayer.init;
begin
	gameSprite.init(40,68,$42);
	reset;
end;

procedure tPlayer.reset;
begin
	with cSprite^ do begin
		currentX:=40;
		currentY:=68;
		currentTile:=$4C;
		oldX:=currentX;
		oldY:=currentY;
	end;
	direction:=3;
	inputdirection:=3;
	altInputDirection:=5;
	waka:=0;
end;

procedure tPlayer.update;
var
	mx,my,
	tx,ty,
	dCount,oldDirection:integer;
	t:word;

	procedure eatDot;
	begin
		tileMap[ty,tx]:=$28;
		if (soundSource=sound_pcSpeaker) then begin
			pakuSound:=6;
		end else begin
			pakuSound:=8;
		end;
		scorePoints(10);
		buffer_nullTile4x3(tx*3+1,ty*3);
		dec(dots);
		globalDotCounter:=0;
		if globalJailCounterEnable then begin
			inc(globalJailCounter);
			if globalJailCounter>7 then begin
				ghost[2].exitCounter:=0;
				if globalJailCounter>14 then begin
					ghost[3].exitCounter:=0;
				end;
			end;
		end else begin
			t:=0;
			repeat
				with ghost[t] do begin
					if inJail and (exitCounter>0) then begin
						dec(exitCounter);
						t:=4;
					end else inc(t);
				end;
			until t>=4;
		end;
	end;

begin
	with (cSprite^) do begin

		dCount:=1;
		oldDirection:=direction;

		mx:=currentX mod 3;
		my:=(currentY+1) mod 3;

		tx:=(currentX+1) div 3;
		ty:=(currentY+1) div 3;

		if (
			(direction=move_left) and
			(currentX=0) and
			(currentY=41)
		) then begin
			tg_bar(0,41,4,45,0);
			currentX:=81;
			oldX:=currentX;
		end else if (
			(direction=move_right) and
			(currentX=81) and
			(currentY=41)
		) then begin
			tg_bar(81,41,85,45,0);
			currentX:=0;
			oldX:=currentX;
		end;

		case tileMap[ty,tx] of
			$20:eatDot;
			$21:begin
				eatDot;
				globalFleeCounter:=levelData[truncLevel].frightTime;
				nextGhost:=200;
				for t:=0 to 3 do with ghost[t] do begin
					if (
						not(inJail) and
						not(mode=mode_eaten)
					) then begin
						mode:=mode_flee;
						direction:=(direction+2) mod 4;
						fleeCounter:=globalFleeCounter;
					end;
				end;
			end;
		end;

		case direction of
			move_up,move_down:begin
				case inputDirection of
					move_up,move_down:direction:=inputDirection;
					else if (my=0) then begin
						case inputDirection of
							move_right:if tileMap[ty,tx+1]>0 then direction:=inputDirection;
							move_left:if tileMap[ty,tx-1]>0 then direction:=inputDirection;
						end;
					end;
				end;
			end;
			move_left,move_right:begin
				case inputDirection of
					move_left,move_right:direction:=inputDirection;
					else if (mx=0) then begin
						case inputDirection of
							move_up:if tileMap[ty-1,tx]>0 then direction:=inputDirection;
							move_down:if tileMap[ty+1,tx]>0 then direction:=inputDirection;
						end;
					end;
				end;
			end;
		end;

		if (
			not(player.altInputDirection=5) and
			(direction=oldDirection)
		) then case altInputDirection of
			move_up:if (mx=0) and (tileMap[ty-1,tx]>0) then direction:=altInputDirection;
			move_down:if (mx=0) and (tileMap[ty+1,tx]>0) then direction:=altInputDirection;
			move_right:if (my=0) and (tileMap[ty,tx+1]>0) then direction:=altInputDirection;
			move_left:if (my=0) and (tileMap[ty,tx-1]>0) then direction:=altInputDirection;
		end;

		if (
			not(direction=oldDirection) and
			not(direction=(oldDirection+2) mod 4)
		) then begin
			inc(dCount);
		end;
		case direction of
			move_up:if my=0 then begin
				if tileMap[ty-1,tx]>0 then dec(currentY,dCount) else waka:=4;
			end else dec(currentY);
			move_right:if mx=0 then begin
				if tileMap[ty,tx+1]>0 then inc(currentX,dCount) else waka:=4;
			end else inc(currentX);
			move_down:if my=0 then begin
				if tileMap[ty+1,tx]>0 then inc(currentY,dCount) else waka:=4;
			end else inc(currentY);
			move_left:if mx=0 then begin
				if tileMap[ty,tx-1]>0 then dec(currentX,dCount) else waka:=4;
			end else dec(currentX);
		end;

		currentTile:=$40+direction*4+waka div 2;
	end;

	waka:=(waka+1) mod 8;

end;

constructor tGhost.init(sLogic:integer);
begin
	logic:=sLogic;
	cSprite:=buffer_addSprite(0,0,0,5,@spriteTiles);
	reset;
end;

procedure tGhost.reset;
begin
	tileOffset:=logic*8;
	activeTileOffset:=tileOffset;
	frame:=0;
	alternate:=false;
	with cSprite^ do with ghostData[logic] do begin
		direction:=d;
		homeX:=hx;
		homeY:=hy;
		currentX:=cx;
		currentY:=cy;
		oldX:=currentX;
		oldY:=currentY;
		mode:=mode_scatter;
		currentTile:=activeTileOffset+(frame div 4)+direction*2;
		exitCounter:=0;
		case logic of
			2:if level=1 then inc(exitCounter,30);
			3:if level=1 then inc(exitCounter,60) else inc(exitCounter,50);
		end;
	end;
	inputDirection:=direction;
end;

procedure tGhost.update;
var
	dx,dy,
	adx,ady,
	sdx,sdy,
	mx,my,
	tx,ty,
	testAmt,
	r10,
	howFar:integer;
	allowTurn,jailSkip:boolean;
begin
	r10:=random(10);
	alternate:=not(alternate);

	with (cSprite^) do begin

		mx:=currentX mod 3;
		my:=(currentY+1) mod 3;

		tx:=(currentX+1) div 3;
		ty:=(currentY+1) div 3;

		inJail:=(
			(currentX>=jail_left) and
			(currentX<=jail_right) and
			(currentY>=jail_top) and
			(currentY<=jail_bottom)
		);

		if (inJail) then begin

			if (mode=mode_eaten) and (currentY<jail_bottom) then begin
				inc(currentY);
				if (currentY=jail_bottom) then begin
					mode:=mode_scatter;
				end;
			end else begin

				if exitCounter>0 then case currentY of
					jail_top:begin
						direction:=move_down;
						inc(currentX);
					end;
					jail_bottom:begin
						direction:=move_up;
						dec(currentX);
					end;
				end else begin
					case currentX of
						jail_left..39:direction:=move_right;
						41..jail_right:direction:=move_left;
						40:direction:=move_up;
					end;
				end;

				case direction of
					move_up:dec(currentY);
					move_right:inc(currentX);
					move_down:inc(currentY);
					move_left:dec(currentX);
				end;
			end;

		end else begin

			if (
				(direction=move_left) and
				(currentX=0) and
				(currentY=41)
			) then begin
				tg_bar(0,41,4,45,0);
				currentX:=81;
				oldX:=currentX;
			end else if (
				(direction=move_right) and
				(currentX=81) and
				(currentY=41)
			) then begin
				tg_bar(81,41,85,45,0);
				currentX:=0;
				oldX:=currentX;
			end;

			howFar:=1;
			jailSkip:=false;

			case mode of
				mode_scatter:begin
					dx:=homeX-currentX;
					dy:=homeY-currentY;
				end;
				mode_chase:begin
					case logic of
						blinky:begin
							dx:=player.cSprite^.currentX-currentX;
							dy:=player.cSprite^.currentY-currentY;
						end;
						pinky:begin
							case player.direction of
								move_up:begin
									dx:=player.cSprite^.currentX-12;
									dy:=player.cSprite^.currentY-12;
								end;
								move_down:begin
									dx:=player.cSprite^.currentX;
									dy:=player.cSprite^.currentY+12;
								end;
								move_left:begin
									dx:=player.cSprite^.currentX-12;
									dy:=player.cSprite^.currentY;
								end;
								move_right:begin
									dx:=player.cSprite^.currentX+12;
									dy:=player.cSprite^.currentY;
								end;
							end;
							dx:=dx-currentX;
							dy:=dy-currentY;
						end;
						inky:begin
							case player.direction of
								move_up:begin
									dx:=player.cSprite^.currentX-6;
									dy:=player.cSprite^.currentY-6;
								end;
								move_down:begin
									dx:=player.cSprite^.currentX;
									dy:=player.cSprite^.currentY+6;
								end;
								move_left:begin
									dx:=player.cSprite^.currentX-6;
									dy:=player.cSprite^.currentY;
								end;
								move_right:begin
									dx:=player.cSprite^.currentX+6;
									dy:=player.cSprite^.currentY;
								end;
							end;
							dx:=((dx+ghost[blinky].cSprite^.currentX) div 2)-currentX;
							dy:=((dy+ghost[blinky].cSprite^.currentY) div 2)-currentY;
						end;
						clyde:begin
							dx:=player.cSprite^.currentX-currentX;
							dy:=player.cSprite^.currentY-currentY;
							testAmt:=round(sqrt(dx*dx+dy*dy));
							if (testAmt<7) then begin
								dx:=homeX-currentX;
								dy:=homeY-currentY;
							end;
						end;
					end;
				end;
				mode_flee:begin
					dx:=currentX-player.cSprite^.currentX;
					dy:=currentY-player.cSprite^.currentY;
					dec(fleeCounter,frameSpeed);
					if (fleeCounter<=0) then mode:=ghostMode;
				end;
				mode_eaten:begin
					if (
						((currentX=40) or (currentX=41)) and
						(currentY>=31) and
						(currentY<jail_bottom)
					) then begin
						direction:=move_down;
						jailSkip:=true;
					end else begin
						dx:=40-currentX;
						dy:=32-currentY;
						inc(howFar);
					end;
				end;
			end;

			if not(jailSkip) then begin

				adx:=abs(dx);
				ady:=abs(dy);

				case direction of
					move_up,move_left:testAmt:=-1;
					else testAmt:=1;
				end;

				case direction of

					move_up,move_down:if (my=0) then begin
						allowTurn:=(
							(r10>1) and
							((currentX=27) or (currentX=54)) and
							(currentY>26) and
							(currentY<56)
						);
						if (
							(adx>ady) or
							(tileMap[ty+testAmt,tx]=0) or
							(dy>0) or
							allowTurn
							or (r10>0)
						) then begin
							if dx>0 then begin
								if tileMap[ty,tx+1]>0 then begin
									direction:=move_right;
								end else if (
									(tileMap[ty,tx-1]>0) and
									((tileMap[ty+testAmt,tx]=0) or (allowTurn and (currentX=54)))
								) then begin
									direction:=move_left;
								end;
							end else begin
								if tileMap[ty,tx-1]>0 then begin
									direction:=move_left;
								end else if (
									(tileMap[ty,tx+1]>0) and
									((tileMap[ty+testAmt,tx]=0) or (allowTurn and (currentX=27)))
								) then begin
									direction:=move_right;
								end;
							end;
						end;
					end;

					move_left,move_right:if (mx=0) then begin
						allowTurn:=not(
							(r10>0) and
							((currentX>27) and (currentX<54)) and
							((currentY=32) or (currentY=68))
						);
						if (ady>adx) or (tileMap[ty,tx+testAmt]=0) or (dx>0) or (r10>0) then begin
							if dy>0 then begin
								if tileMap[ty+1,tx]>0 then begin
									direction:=move_down;
								end else if (
									(tileMap[ty-1,tx]>0) and
									(tileMap[ty,tx+testAmt]=0) and
									allowTurn
								) then begin
									direction:=move_up;
								end;
							end else begin
								if (tileMap[ty-1,tx]>0) and allowTurn then begin
									direction:=move_up;
								end else if (tileMap[ty+1,tx]>0) and (tileMap[ty,tx+testAmt]=0) then begin
									direction:=move_down;
								end;
							end;
						end;
					end; { left/right }

				end; {case }

			end; { not jailskip }

			if not(
				(
					(mode=mode_flee) or (
						(currentY=41) and (
							(currentX<18) or (currentX>62)
						)
					)
				) and alternate
			)
			 then case direction of
				move_up:if my=0 then begin
					if tileMap[ty-1,tx]>0 then dec(currentY,howfar);
				end else dec(currentY);
				move_right:if mx=0 then begin
					if tileMap[ty,tx+1]>0 then inc(currentX,howfar);
				end else inc(currentX);
				move_down:if my=0 then begin
					if (tileMap[ty+1,tx]>0) or jailSkip then inc(currentY,howfar);
				end else inc(currentY);
				move_left:if mx=0 then begin
					if tileMap[ty,tx-1]>0 then dec(currentX,howfar);
				end else dec(currentX);
			end;

		end;
		case mode of
			mode_eaten:currentTile:=$30+(frame div 4)+direction*2;
			mode_flee:if (
				(fleeCounter<levelData[truncLevel].flashes) and
				((fleeCounter mod 120) div 60=0)
			) then begin
				currentTile:=$28+(frame div 4)+direction*2;
			end else currentTile:=$20+(frame div 4)+direction*2;
			else currentTile:=activeTileOffset+(frame div 4)+direction*2;
		end;
	end;
	frame:=(frame+1) mod 8;
end;

procedure setupTileMap;
var
	px,py,t:word;
	mapOffset,storeOffset:pTileData;
	c:byte;
begin
	mapOffset:=map.dataStart;
	storeOffset:=pTileData(@tileMap);
	while not(mapOffset=map.dataEnd) do begin
		if (mapOffset^ and $80)=$80 then begin
			c:=mapOffset^ and $7F;
			inc(mapOffset);
		end else c:=1;
		while c>0 do begin
			case mapOffset^ of
				$20,$21,$28:storeOffset^:=mapOffset^;
				else storeOffset^:=0;
			end;
			inc(storeOffset);
			dec(c);
		end;
		inc(mapOffset);
	end;
end;

procedure loadData;
var
	t:word;
	l:longint;
begin
	writeln(memAvail);
	if (memAvail<10000) then begin
		writetext('memory');
		halt;
	end;
	writeln('Loading Data');
	map.init('MAP');
	mapTiles.init('MAPTILES');
	spriteTiles.init('SPRITES');
	new(fonts,init('FONTS'));
	scoreList.init;
	setupTileMap;
	writeln('Initializing Sprites System');
	for t:=0 to 3 do begin
		ghost[t].init(t);
	end;
	player.init;
	bonus.init;
	writeln;
	write('Starting Timer');
	startTimer;
	timerInterval:=getUserClockInterval(120);
	writeln(' - Complete');
	write('Starting Sound');
	startSound;
	writeln(' - Complete');
	if paramExists('/debug') then begin
		writeln;
		writeln('Dos Free:',dosAvail);
		writeln('Heap Free:',memAvail);
		writeln('Stack Free:',stackAvail);
		writeln;
		writeln('Press any key to continue');
		waitkey;
	end;
end;

procedure cleanup;
var
	t:word;
begin
	scoreList.term;
	write('Halted: Graphics');
	killSound;
	write(' Sound');
	killTimer;
	write(' Timer - Releasing Memory');
	dispose(fonts);
	spriteTiles.term;
	mapTiles.term;
	map.term;
	writeln(' - Complete');
end;

procedure eraseReady;
var
	t,x:word;
begin
	x:=32;
	repeat
		buffer_show8x6(x,50);
		inc(x,6);
	until x>=56;
end;

{ map is stored RLE, so we need to decode that }
procedure drawPlayfield;
var
	px,py:word;
	mapOffset:pTileData;
	c:byte;

begin
	px:=fieldOffsetX;
	py:=0;
	mapOffset:=map.dataStart;
	buffer_sourceClear(0);
	buffer_sourceRender;
	while not(mapOffset=map.dataEnd) do begin
		if (mapOffset^ and $80)=$80 then begin
			{
				bit 7 set, bottom bits holds how many times
				to repeat next byte. Store the repeat and move
				the pointer ahead one.
			}
			c:=mapOffset^ and $7F;
			inc(mapOffset);
		end else c:=1; { otherwise it's just 1 raw data byte }

		while (c>0) do begin
			buffer_tile3(px,py,mapTiles.dataStart,mapOffset^);
			inc(px,3);
			if (px>=fieldEndX) then begin
				px:=fieldOffsetX;
				inc(py,3);
				if (py>93) then py:=0;
			end;
			dec(c);
		end;

		inc(mapOffset);
	end;
	buffer_sourceBackground;
	buffer_copySource2Dest;
	buffer_copyDest2Screen;
end;

procedure resetSprites;
var
	t:word;
begin
	for t:=0 to 3 do ghost[t].reset;
	player.reset;
	bonus.reset;
end;

procedure setupPlayfield;
var
	t,n,x:word;
	b:byte;
begin
	resetSprites;
	setupTileMap;
	drawPlayField;
	fonts^.outtext(104,12,'HIGH SCORE',7);
	fonts^.outtext(104,30,'YOUR SCORE',7);
	fonts^.outDecPadded(108,36,score,3);
	fonts^.outDecPadded(108,18,highScore,3);
	x:=78;
	t:=level;
	if (t>9) then begin
		n:=9;
	end else n:=t;
	while (n>0) do begin
		if (t>21) then begin
			b:=levelData[21].symbol;
		end else b:=levelData[t].symbol;
		tg_tile5(x,94,spriteTiles.dataStart,b);
		x:=x-6;
		dec(n);
		dec(t);
	end;
	showLives;
	showAudioStatus;
	buffer_updateSprites;
end;

procedure behaviorUpdate;
var
	t:word;
begin
	case level of
		1:begin
			behaviorCounter:=behaviorLevel1[behaviorStep].time;
			ghostMode:=behaviorLevel1[behaviorStep].behavior;
		end;
		2..4:begin
			behaviorCounter:=behaviorLevel2_4[behaviorStep].time;
			ghostMode:=behaviorLevel2_4[behaviorStep].behavior;
		end;
		else begin
			behaviorCounter:=behaviorLevel5Plus[behaviorStep].time;
			ghostMode:=behaviorLevel5Plus[behaviorStep].behavior;
		end;
	end;
	for t:=0 to 3 do with ghost[t] do begin
		if (
			not(inJail) and
			not(mode=mode_flee) and
			not(mode=mode_eaten)
		) then mode:=ghostMode;
	end;
	if (behaviorStep<7) then inc(behaviorStep);
end;

procedure eatGhostScore(x,y,points:integer);
var
	t,x1:integer;
begin
	scorePoints(points);
	x1:=x-2;
	if (points>999) then dec(x1,2);
	fonts^.outDecNonPadded(x1,y,points,11);
	playEatGhost;
	for t:=0 to 2 do begin
		buffer_show8x6(x1,y);
		inc(x1,6);
	end;
end;

procedure testCollisions;
var
	dx,dy,sx,sy,ex,ey:integer;
	t,n:word;
begin

	with bonus.cSprite^ do begin
		sx:=currentX-2;
		ex:=currentX+2;
		sy:=currentY-2;
		ey:=currentY+2;
	end;

	with player.cSprite^ do begin
		if (
			(bonus.showCounter>0) and
			(currentX>sx) and
			(currentX<ex) and
			(currentY>sy) and
			(currentY<ey)
		) then begin
			bonus.reset;
			eatGhostScore(currentX,currentY,levelData[truncLevel].points);
		end;
	end;

	for t:=0 to 3 do with ghost[t] do begin
		dx:=abs(cSprite^.currentX-player.cSprite^.currentX);
		if (dx<2) then begin
			dy:=abs(cSprite^.currentY-player.cSprite^.currentY);
			if (dy<2) then begin
				case mode of
					mode_flee:with cSprite^ do begin
						eatGhostScore(currentX,currentY,nextGhost);
						nextGhost:=nextGhost*2;
						mode:=mode_eaten;
					end;
					mode_chase,mode_scatter:begin
						if soundOn then killVoices;
						buffer_hideSprites;
						n:=0;
						repeat
							if userTimerExpired(timerInterval) then begin
								if soundOn then begin
									case n of
										0..80:outFreq(0,880-n*5+trunc(220*sin(n/1.5)),$0B,0);
										85,100:outFreq(0,0,0,0);
										90,110:outFreq(0,55,$0F,0);
									end;
								end;
								with player.cSprite^ do begin
									buffer_copySourceDest8x8(currentX,currentY);
									bufferWriteTile(
										currentX,currentY,
										spriteTiles.dataStart,
										$50+n div 15
									);
									bufferShow(currentX,currentY);
								end;
								inc(n);
							end;
						until n>=120;
						if soundOn then outFreq(0,0,0,0);
						wait(45);
						with player.cSprite^ do begin
							buffer_copySourceDest8x8(currentX,currentY);
							bufferShow(currentX,currentY);
						end;
						dec(lives);
						showLives;
						if (lives>0) then begin
							resetSprites;
							buffer_updateSprites;
							fonts^.outTextCentered(43,50,readyString,14);
							wait(120);
							eraseReady;
						end else begin
							fonts^.outtextCentered(44,40,'GAME OVER',14);
							wait(240);
						end;
					end;
				end;
			end;
		end;
	end
end;

procedure blinkPellets;
var
	t:word;
begin
	pelletBlinkCounter:=(pelletBlinkCounter+1) mod 6;
	case pelletBlinkCounter of
		0:begin
			for t:=0 to 3 do with pelletData[t] do begin
				if tileMap[tileY,tileX]=$21 then buffer_nullTile4x3(x,y);
			end;
		end;
		3:begin
			buffer_sourceRender; { make dest be background }
			for t:=0 to 3 do with pelletData[t] do begin
				if tileMap[tileY,tileX]=$21 then buffer_tile3(x,y,mapTiles.dataStart,$21);
			end;
			buffer_sourceBackground; { make dest be background }
		end;
	end;
end;

procedure menuBlinkPellets;
var
	t:word;
begin
	pelletBlinkCounter:=(pelletBlinkCounter+1) mod 6;
	case pelletBlinkCounter of
		0:for t:=0 to 3 do with pelletData[t] do begin
			tg_bar(x,y,x+2,y+2,0);
		end;
		3:for t:=0 to 3 do with pelletData[t] do begin
			tg_Tile3(x,y,mapTiles.dataStart,$21);
		end;
	end;
end;

procedure gameLoop;
var
	t,frameThrottle,altThrottle:word;
	firstRun:boolean;
begin

	pakuSound:=0;
	siren:=0;
	score:=0;
	level:=startingLevel;
	lives:=startingLives;
	firstRun:=true;

	repeat

		dots:=startingDots;

		inc(level);

		if (level>21) then truncLevel:=21 else truncLevel:=level;

		setupPlayfield;

		bonus.reset;

		fonts^.outTextCentered(43,50,readyString,14);

		if firstRun and soundOn then begin
			firstRun:=false;
			playTheme;
			wait(30);
		end else wait(120);

		eraseReady;

		frameSpeed:=levelData[truncLevel].rate;
		frameThrottle:=frameSpeed-1;


		globalJailCounterEnable:=false;
		globalJailCounter:=0;
		globalDotCounter:=0;
		fleeTotal:=0;

		behaviorStep:=0;
		behaviorUpdate;
		altThrottle:=0;
		pelletBlinkCounter:=0;
		gainLifeCounter:=0;

		nextGhost:=200;

		userClockCounter:=0;

		repeat

			if userTimerExpired(timerInterval) then begin

				frameThrottle:=(frameThrottle+1) mod frameSpeed;
				{
					dividing up the tasks across multiple user timer ticks
					makes for smoother gameplay on 4.77mhz machines
				}
				case frameThrottle of
					0:buffer_updateSprites;
					1:if joystickOn then checkStickDirection;
					2:begin
						player.update;
						fleeTotal:=0;
						for t:=0 to 3 do begin
							ghost[t].update;
							if ghost[t].mode=mode_flee then inc(fleeTotal);
						end;
						if (lives>0) then testCollisions;
						case dots of
							fruitCounter1,fruitCounter2:bonus.activate;
						end;
						bonus.update;
						blinkPellets;
					end;
				end;


				if (globalDotCounter<120) then begin
					inc(globalDotCounter);
				end else begin
					globalDotCounter:=0;
					if ghost[2].exitCounter>0 then dec(ghost[2].exitCounter);
					if ghost[3].exitCounter>0 then dec(ghost[3].exitCounter);
				end;

				if (globalFleeCounter>0) then begin
					dec(globalFleeCounter);
					if globalFleeCounter=0 then nextGhost:=200;
				end;

				inc(altThrottle);
				if (altThrottle and $01)=0 then begin
					dec(behaviorCounter);
					if (behaviorCounter=0) then behaviorUpdate;
				end;

				{sound handler }
				if soundOn then begin
					if (frameThrottle and $01)=0 then begin
						siren:=(siren+1) mod 24;
						if (fleeTotal=0) then begin
							t:=sirenLookup[siren];
							case dots of
								0..43:t:=(t*4) div 3;
								44..143:t:=(t*5) div 4;
							end;
							outFreq(0,t,$0B,0);
						end else begin
							outFreq(0,440-(siren mod 12)*20,$0B,0);
						end;
					end;
					if (gainLifeCounter>0) then begin
						dec(gainLifeCounter);
						case gainLifeCounter of
							0:outFreq(2,0,0,0);
							else outFreq(2,660-(gainLifeCounter*11) div 3,$0C,2);
						end;
					end;
					if (pakuSound>0) then begin
						dec(pakuSound);
						case pakuSound of
							0:outFreq(1,0,0,0);
							else outFreq(1,220+pakusound*$30,$0F,1);
						end;
					end;
				end;

			end; { userTimerExpired }

			gameKeyCheck;

		until (lives<=0) or (dots<=0);

		if soundOn then killVoices;

		if (dots=0) then begin
			wait(90);
			buffer_hideSprites;
			tg_bar(jail_left,jail_top,jail_right,jail_bottom+5,0);
			frameThrottle:=0;
			player.cSprite^.currentTile:=$43;

			whitePlayField;
			buffer_sourceBackground;
			repeat
				wait(30);
				case frameThrottle of
					0,2,4:showDestPlayField;
					1,3,5:begin
						buffer_sourceRender;
						showDestPlayField;
						buffer_sourceBackground;
					end;
				end;
				inc(frameThrottle);
			until frameThrottle>6;
			wait(60);
		end;
		buffer_copySource2Dest;
		buffer_hideSprites;

	until (level<=0) or (lives<=0);

	scoreList.update;

end;

procedure drawMenu;
var
	t,n,v:word;
begin
	level:=0;
	lives:=startingLives;
	userClockCounter:=0;
	tg_clear(0);
	drawPlayField;
	showLives;
	n:=91;
	v:=32;
	for t:=0 to 7 do begin
		tg_tile5(37+t*6,94,spriteTiles.dataStart,t+$38);
		if t<6 then begin
			tg_tile5(n,3,spriteTiles.dataStart,$60+t);
			tg_tile5(n,8,spriteTiles.dataStart,$66+t);
			inc(n,36);
			tg_tile5(n,3,spriteTiles.dataStart,$60+t);
			tg_tile5(n,8,spriteTiles.dataStart,$66+t);
			dec(n,31);
			if t<4 then with ghostData[t] do begin
				fonts^.outText(98,v,nickname,color or 8);
				fonts^.outText(127,v,behavior,color);
				inc(v,7);
			end;
			if (t>0) and (t<=menuTextCount) then with menuText[t] do begin
				fonts^.outText(x,y,data,7);
			end;
		end;
	end;
	tg_tile5(120,7,spriteTiles.dataStart,$50);
	tg_tile3(101,74,mapTiles.dataStart,$20);
	showAudioStatus;
	scoreBox;
end;

procedure menuLoop;
var
	ch:char;
	t,n,
	counter,
	borderCounter:word;
	sc:longint;
begin
	for t:=0 to 3 do with pelletData[t] do updateList.add(x,y);
	ch:=#255;
	score:=0;
	counter:=0;
	borderCounter:=0;
	soundOn:=true;
	drawMenu;
	userClockCounter:=0;
	repeat
		if userTimerExpired(timerInterval) then begin
			counter:=(counter+1) and $1F;
			case counter of
				$00,$10:begin
					n:=32;
					if (counter=$00) then t:=0 else t:=1;
					repeat
						buffer_copySourceDest8x8(90,n);
						buffer_tile5(90,n,spriteTiles.dataStart,t);
						buffer_show8x6(89,n);
						inc(n,7);
						inc(t,8);
					until t>=32;
				end;
			end;
			case counter of
				$00,$08,$10,$18:begin
					for t:=0 to 70 do begin
						tg_putpixel(87+t,29,borderColors[borderCounter]);
						tg_putpixel(157-t,60,borderColors[borderCounter]);
						borderCounter:=(borderCounter+1) mod 3;
					end;
					for t:=1 to 31 do begin
						tg_putpixel(87,60-t,borderColors[borderCounter]);
						tg_putpixel(157,29+t,borderColors[borderCounter]);
						borderCounter:=(borderCounter+1) mod 3;
					end;
					inc(borderCounter);
				end;
				$02,$0A,$12,$1A:begin
					menuBlinkPellets;
					case pelletBlinkCounter of
						0:tg_bar(101,80,103,82,0);
						3:tg_tile3(101,80,mapTiles.dataStart,$21);
					end;
				end;
				$0C:begin
					fonts^.outtext(98,15,'ENTER',10);
					fonts^.outtext(103,21,'ESC',2);
				end;
				$1C:begin
					fonts^.outtext(98,15,'ENTER',2);
					fonts^.outtext(103,21,'ESC',10);
				end;
			end;
		end;
		if keypressed then begin
			ch:=readkey;
			if ch=#13 then begin
				gameloop;
				drawMenu;
			end else checkGlobalKeys(ch);
		end else if joystickOn then begin
			if (joystickButton(0) or joystickButton(1)) then begin
				gameloop;
				drawMenu;
			end;
		end;
	until ch=#27;
	killVoices;
	flushKeyBuffer;
end;

begin
	writeln(titleString);
	if paramExists('/?') or paramExists('/help') then begin
		writeText('help');
	end else begin
		loadData;
		tg_init;
		joystickAllow:=paramExists('/joy');
		if joystickAllow then begin
			tg_clear(1);
			joystickOn:=calibrateStick(79);
		end;
		menuLoop;
		tg_term;
		writeln(titleString);
		cleanup;
	end;
	writeText('disclaimer');
end.fpc